package com.derbysoft.nuke.configkeeper.utils;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class JSON {

    private ObjectMapper objectMapper;

    private JSON() {
        objectMapper = new ObjectMapper();
    }

    private static class JSONHolder {
        private static final JSON INSTANCE = new JSON();

        private JSONHolder() {
        }
    }

    public static final JSON get() {
        return JSONHolder.INSTANCE;
    }


    public Object deserialize(Reader reader) throws JSONParseException {
        try {
            return objectMapper.readValue(reader, Object.class);
        } catch (IOException e) {
            throw new JSONParseException("parse json fail", e);
        }
    }

    public <T> T deserialize(Reader reader, Class<T> valueType) throws JSONParseException {
        try {
            return objectMapper.readValue(reader, valueType);
        } catch (IOException e) {
            throw new JSONParseException("parse json fail", e);
        }
    }

    public <T> T deserialize(Reader reader, Type valueType) throws JSONParseException {
        try {
            return objectMapper.readValue(reader, objectMapper.constructType(valueType));
        } catch (IOException e) {
            throw new JSONParseException("parse json fail", e);
        }
    }

    public void serialize(Writer writer, Object object) {
        try {
            objectMapper.writeValue(writer, object);
        } catch (IOException e) {
            throw new JSONGenerationException("generate json fail", e);
        }
    }

    public void serializePretty(Writer writer, Object object) {
        try {
            objectMapper.writerWithDefaultPrettyPrinter().writeValue(writer, object);
        } catch (IOException e) {
            throw new JSONGenerationException("generate json fail", e);
        }
    }

    public String toJSONString(Object object) {
        try {
            return objectMapper.writeValueAsString(object);
        } catch (IOException e) {
            throw new JSONGenerationException("generate json fail", e);
        }
    }

    public String toPrettyJSONString(Object object) {
        try {
            return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
        } catch (IOException e) {
            throw new JSONGenerationException("generate json fail", e);
        }
    }

    public <T> T toJavaObject(String jsonString, Type valueType) {
        if (jsonString == null || jsonString.length() == 0) {
            return null;
        }
        try {
            return deserialize(new StringReader(jsonString), valueType);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("invalid json value " + jsonString, e);
        }
    }

    public <T> List<T> toList(String jsonString, Class<T> valueType) {
        if (jsonString == null || jsonString.length() == 0) {
            return null;
        }
        try {
            return deserialize(new StringReader(jsonString), SimpleParameterizedType.createCollectionType(List.class, valueType));
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("invalid json value " + jsonString, e);
        }
    }

    public <T> Set<T> toSet(String jsonString, Class<T> valueType) {
        if (jsonString == null || jsonString.length() == 0) {
            return null;
        }
        try {
            return deserialize(new StringReader(jsonString), SimpleParameterizedType.createCollectionType(Set.class, valueType));
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("invalid json value " + jsonString, e);
        }
    }

    public <K, V> Map<K, V> toMap(String jsonString, Class<K> keyType, Class<V> valueType) {
        if (jsonString == null || jsonString.length() == 0) {
            return null;
        }
        try {
            return deserialize(new StringReader(jsonString), SimpleParameterizedType.createMapType(Map.class, keyType, valueType));
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("invalid json value " + jsonString, e);
        }
    }

    public static class SimpleParameterizedType implements ParameterizedType {
        private Class ownerType;
        private Type[] argumentTypes;

        public static SimpleParameterizedType createCollectionType(Class collectionClass, Class itemType) {
            SimpleParameterizedType simpleParameterizedType = new SimpleParameterizedType();
            simpleParameterizedType.ownerType = collectionClass;
            simpleParameterizedType.argumentTypes = new Type[]{itemType};

            return simpleParameterizedType;
        }

        public static SimpleParameterizedType createMapType(Class mapClass, Class keyType, Class valueType) {
            SimpleParameterizedType simpleParameterizedType = new SimpleParameterizedType();
            simpleParameterizedType.ownerType = mapClass;
            simpleParameterizedType.argumentTypes = new Type[]{keyType, valueType};

            return simpleParameterizedType;
        }

        @Override
        public Type[] getActualTypeArguments() {
            return argumentTypes;
        }

        @Override
        public Type getRawType() {
            return ownerType;
        }

        @Override
        public Type getOwnerType() {
            return ownerType;
        }
    }
}
